// PanelSplitFile.cpp

#include "StdAfx.h"

#include "App.h"

#include "../C/7zCrc.h"
#include "../C/Sha256.h"

#include "Common/FileSystem/FileFind.h"
#include "Common/Stream/FileIO.h"

#include "Common/String/FormatUtils.h"

#include "Common/Props/PropIDUtils.h"

#include "ProgressDialog2.h"
#include "FileManager/Buffer.h"
#include "FileManager/Windows.h"


using namespace Common;

using namespace Common::FileSystem;

static const UINT32 kBufSize = (1 << 15);

struct CDirEnumerator
{
	bool FlatMode;
	CString BasePrefix;
	CStringVector FileNames;

	std::vector<std::shared_ptr<Find::CEnumerator> > Enumerators;
	CStringVector Prefixes;
	CStringVector::size_type Index;
	HRESULT GetNextFile(File::CFileInfo &fileInfo, bool &filled, CString &fullPath);
	void Init();

	CDirEnumerator(): FlatMode(false) {};
};

void CDirEnumerator::Init()
{
	Enumerators.clear();
	Prefixes.clear();
	Index = 0;
}

static HRESULT GetNormalizedError()
{
	HRESULT errorCode = GetLastError();
	return (errorCode == 0) ? E_FAIL : errorCode;
}

HRESULT CDirEnumerator::GetNextFile(File::CFileInfo &fileInfo, bool &filled, CString &resPath)
{
	filled = false;
	for (;;)
	{
		if (Enumerators.empty())
		{
			if (Index >= FileNames.size())
			{
				return S_OK;
			}
			const CString &path = FileNames[Index];
			int pos = path.ReverseFind(CHAR_PATH_SEPARATOR);
			resPath.Empty();
			if (pos >= 0)
			{
				resPath = path.Left(pos + 1);
			}

#ifdef _WIN32
			// it's for "c:" paths/
			if (BasePrefix.IsEmpty() && path.GetLength() == 2 && path[1] == ':')
			{
				fileInfo.Name = path;
				fileInfo.Attributes = FILE_ATTRIBUTE_DIRECTORY;
				fileInfo.Size = 0;
			}
			else
#endif
				if (!fileInfo.Find(BasePrefix + path))
				{
					DWORD errorCode = GetNormalizedError();
					resPath = path;
					return errorCode;
				}
			Index++;
			break;
		}
		bool found;
		if (!Enumerators.back()->Next(&fileInfo, found))
		{
			HRESULT errorCode = GetNormalizedError();
			resPath = Prefixes.back();
			return errorCode;
		}
		if (found)
		{
			resPath = Prefixes.back();
			break;
		}
		Enumerators.pop_back();
		Prefixes.pop_back();
	}
	resPath += fileInfo.Name;
	if (!FlatMode && fileInfo.IsDir())
	{
		CString prefix = resPath + CHAR_PATH_SEPARATOR;
		std::shared_ptr<Find::CEnumerator> value = std::make_shared<Find::CEnumerator>(BasePrefix + prefix + (CString)(wchar_t)FileName::kAnyStringWildcard);
		Enumerators.push_back(value);
		Prefixes.push_back(prefix);
	}
	filled = true;
	return S_OK;
}

static void ConvertByteToHex(unsigned value, wchar_t* s)
{
	for (int i = 0; i < 2; i++)
	{
		unsigned t = value & 0xF;
		value >>= 4;
		s[1 - i] = (wchar_t)((t < 10) ? (_T('0') + t) : (_T('A') + (t - 10)));
	}
}

class CThreadCrc: public CProgressThreadVirt
{
	UINT64 NumFilesScan;
	UINT64 NumFiles;
	UINT64 NumFolders;
	UINT64 DataSize;
	UINT32 DataCrcSum;
	BYTE Sha256Sum[SHA256_DIGEST_SIZE];
	UINT32 DataNameCrcSum;

	CString GetResultMessage() const;
	HRESULT ProcessVirt();
public:
	CDirEnumerator Enumerator;
	CThreadCrc(CProgressThreadVirt::ThreadPoolType* pool) :
		CProgressThreadVirt(pool)
	{}

};

CString CThreadCrc::GetResultMessage() const
{
	CString s;
	TCHAR sz[32];

	s += Lang::LangString(IDS_FILES_COLON, 0x02000320);
	s += _T(' ');
	_ui64tot(NumFiles, sz, 10);
	s += sz;
	s += _T('\n');

	s += Lang::LangString(IDS_FOLDERS_COLON, 0x02000321);
	s += _T(' ');
	_ui64tot(NumFolders, sz, 10);
	s += sz;
	s += _T('\n');

	s += Lang::LangString(IDS_SIZE_COLON, 0x02000322);
	s += _T(' ');
	_ui64tot(DataSize, sz, 10);
	s += String::PatternFormat(IDS_FILE_SIZE, 0x02000982, sz);
	s += _T('\n');

	s += Lang::LangString(IDS_CHECKSUM_CRC_DATA, 0x03020721);
	s += _T(' ');
	Props::ConvertUInt32ToHex(DataCrcSum, sz);
	s += sz;
	s += _T('\n');

	s += Lang::LangString(IDS_CHECKSUM_CRC_DATA_NAMES, 0x03020722);
	s += _T(' ');
	Props::ConvertUInt32ToHex(DataNameCrcSum, sz);
	s += sz;
	s += _T('\n');

	if (NumFiles == 1 && NumFilesScan == 1)
	{
		s += _T("SHA-256: ");
		for (int i = 0; i < SHA256_DIGEST_SIZE; i++)
		{
			wchar_t s2[4];
			ConvertByteToHex(Sha256Sum[i], s2);
			s2[2] = 0;
			s += s2;
		}
	}
	return s;
}

HRESULT CThreadCrc::ProcessVirt()
{
	DataSize = NumFolders = NumFiles = NumFilesScan = DataCrcSum = DataNameCrcSum = 0;
	memset(Sha256Sum, 0, SHA256_DIGEST_SIZE);

	FileManager::CMyBuffer bufferObject;
	if (!bufferObject.Allocate(kBufSize))
	{
		return E_OUTOFMEMORY;
	}
	BYTE* buffer = (BYTE*)(void*)bufferObject;

	UINT64 totalSize = 0;

	Enumerator.Init();

	CString scanningStr = Lang::LangString(IDS_SCANNING, 0x03020800);
	scanningStr += _T(' ');

	CProgressSync &sync = ProgressDialog.Sync;

	HRESULT hRes(S_OK);
	for (;;)
	{
		File::CFileInfo fileInfo;
		bool filled;
		CString resPath;
		HRESULT errorCode = Enumerator.GetNextFile(fileInfo, filled, resPath);
		if (errorCode != 0)
		{
			ErrorPath1 = resPath;
			return errorCode;
		}
		if (!filled)
		{
			break;
		}
		if (!fileInfo.IsDir())
		{
			totalSize += fileInfo.Size;
			NumFilesScan++;
		}
		sync.SetCurrentFileName(scanningStr + resPath);
		sync.SetProgress(totalSize, 0);
		hRes = sync.SetPosAndCheckPaused(0);
		if(S_OK != hRes)
		{
			return hRes;
		}
	}
	sync.SetNumFilesTotal(NumFilesScan);
	sync.SetProgress(totalSize, 0);

	Enumerator.Init();

	for (;;)
	{
		File::CFileInfo fileInfo;
		bool filled;
		CString resPath;
		hRes = Enumerator.GetNextFile(fileInfo, filled, resPath);
		if (S_OK != hRes)
		{
			ErrorPath1 = resPath;
			return hRes;
		}
		if (!filled)
		{
			break;
		}

		UINT32 crc = CRC_INIT_VAL;
		CSha256 sha256;
		Sha256_Init(&sha256);

		if (fileInfo.IsDir())
		{
			NumFolders++;
		}
		else
		{
			Common::IO::CInFile inFile;
			if (!inFile.Open(Enumerator.BasePrefix + resPath))
			{
				hRes = GetNormalizedError();
				ErrorPath1 = resPath;
				return hRes;
			}
			sync.SetCurrentFileName(resPath);
			sync.SetNumFilesCur(NumFiles);
			NumFiles++;
			for (;;)
			{
				UINT32 processedSize;
				if (!inFile.Read(buffer, kBufSize, processedSize))
				{
					hRes = GetNormalizedError();
					ErrorPath1 = resPath;
					return hRes;
				}
				if (processedSize == 0)
				{
					break;
				}
				crc = CrcUpdate(crc, buffer, processedSize);
				if (NumFilesScan == 1)
				{
					Sha256_Update(&sha256, buffer, processedSize);
				}

				DataSize += processedSize;
				hRes = sync.SetPosAndCheckPaused(DataSize);
				if(S_OK != hRes)
				{
					return hRes;
				}
			}
			DataCrcSum += CRC_GET_DIGEST(crc);
			if (NumFilesScan == 1)
			{
				Sha256_Final(&sha256, Sha256Sum);
			}
		}
		for (int i = 0; i < resPath.GetLength(); i++)
		{
			wchar_t c = resPath[i];
			crc = CRC_UPDATE_BYTE(crc, ((BYTE)(c & 0xFF)));
			crc = CRC_UPDATE_BYTE(crc, ((BYTE)((c >> 8) & 0xFF)));
		}
		DataNameCrcSum += CRC_GET_DIGEST(crc);
		hRes = sync.SetPosAndCheckPaused(DataSize);
		if(S_OK != hRes)
		{
			return hRes;
		}
	}
	sync.SetNumFilesCur(NumFiles);
	OkMessage = GetResultMessage();
	OkMessageTitle = Lang::LangString(IDS_CHECKSUM_INFORMATION, 0x03020720);
	return S_OK;
}

void CMainWnd::CalculateCrc()
{
	if (!m_wndPanel.m_directory.IsFsOrDrivesFolder())
	{
		FileManager::CWindows::MessageBoxErrorLang(*this, IDS_OPERATION_IS_NOT_SUPPORTED, 0x03020208);
		return;
	}
	std::vector<UINT32> indices;
	m_wndPanel.GetOperatedItemIndices(indices);
	if (indices.empty())
	{
		return;
	}

	{
		CThreadCrc t(GetUIThreadPool());
		for (size_t i = 0; i < indices.size(); i++)
		{
			t.Enumerator.FileNames.push_back(m_wndPanel.m_directory.GetItemRelPath(indices[i]));
		}
		t.Enumerator.BasePrefix = m_wndPanel.m_directory.GetFsPath();
		t.Enumerator.FlatMode = GetFlatMode();

		t.ProgressDialog.ShowCompressionInfo = false;

		CString title = Lang::LangString(IDS_CHECKSUM_CALCULATING, 0x03020710);

		t.ProgressDialog.MainWindow = _window;
		t.ProgressDialog.MainTitle = Lang::LangString(IDS_APP_TITLE, 0x03000000);
		t.ProgressDialog.MainAddTitle = title + CString(_T(' '));

		if (t.Create(title, _window) != S_OK)
		{
			return;
		}
	}
	RefreshTitleAlways();
}
